package kpython.backend.translator;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import kpython.backend.ir.canon.AccessFunction;
import kpython.backend.ir.canon.AndFunction;
import kpython.backend.ir.canon.DifFunction;
import kpython.backend.ir.canon.DivideFunction;
import kpython.backend.ir.canon.EqEqualFunction;
import kpython.backend.ir.canon.EqGreaterFunction;
import kpython.backend.ir.canon.EqLessFunction;
import kpython.backend.ir.canon.GreaterFunction;
import kpython.backend.ir.canon.LessFunction;
import kpython.backend.ir.canon.MinusFunction;
import kpython.backend.ir.canon.MultiplyFunction;
import kpython.backend.ir.canon.NotFunction;
import kpython.backend.ir.canon.OrFunction;
import kpython.backend.ir.canon.PlusFunction;
import kpython.backend.ir.canon.PrintFunction;
import kpython.backend.ir.canon.RestFunction;
import kpython.backend.ir.canon.WriterFunction;
import kpython.backend.ir.tree.Assignment;
import kpython.backend.ir.tree.Atom;
import kpython.backend.ir.tree.AtomList;
import kpython.backend.ir.tree.CallBlock;
import kpython.backend.ir.tree.Comparison;
import kpython.backend.ir.tree.Expr;
import kpython.backend.ir.tree.FORBlock;
import kpython.backend.ir.tree.FunctionBlock;
import kpython.backend.ir.tree.Goto;
import kpython.backend.ir.tree.IFBlock;
import kpython.backend.ir.tree.IFStmt;
import kpython.backend.ir.tree.IRNode;
import kpython.backend.ir.tree.IRTreeVisitor;
import kpython.backend.ir.tree.Label;
import kpython.backend.ir.tree.ListElement;
import kpython.backend.ir.tree.Not;
import kpython.backend.ir.tree.Operation;
import kpython.backend.ir.tree.PrintBlock;
import kpython.backend.ir.tree.Relational;
import kpython.backend.ir.tree.Return;
import kpython.backend.ir.tree.SimpleBlock;
import kpython.backend.ir.tree.WHILEBlock;
import kpython.backend.semantic.Scope;
import kpython.backend.semantic.Symbol;
import kpython.backend.semantic.SymbolTable;
import kpython.backend.stackframe.PrintableStackFrame;

public class Translator implements IRTreeVisitor {

	public BufferedWriter writer = null;	
	public int ra = 0;
	public SymbolTable symbolTable = null;
	public Scope actual = null;
	
	
	public void initTranslation(SimpleBlock root, SymbolTable symbolTable) {
		try {
			this.symbolTable = symbolTable;
			writer = new BufferedWriter(new FileWriter("data\\result.c"));
			writer.append(TranslatorUtils.generateHeader());
			writer.append("//inicializações\n");
			PrintableStackFrame.initStack(writer);			
			writer.append(TranslatorUtils.generateRegisters());
			actual = this.symbolTable.getScope("main");			
			int count = actual.getNumberSymbols();			
			if(count>0)
				PrintableStackFrame.initLocals(writer, count);
			for(IRNode node: root.getContent()) {				
				writer.append("//**"+node.toString()+"**//\n");
				node.accept(this, null);
			}
			writer.append("goto skip_redirector;\n");
			writer.append("redirector:\n");
			for(int i = 1; i <= ra; i++)
				writer.append("if(_ra == "+i+") goto return"+i+";\n");
			writer.append("skip_redirector:\n");
			writer.append(TranslatorUtils.generateFooter());
			writer.close();
		} catch (IOException e) {			
			e.printStackTrace();
		}		
	}

	@Override
	public Object visit(ListElement node, Object data) {
		return null;
	}

	@Override
	public Object visit(AndFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
	

	@Override
	public Object visit(Return node, Object data) {
		node.expr.accept(this, data);
		return null;
	}
	
	@Override
	public Object visit(AccessFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(DifFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(DivideFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(EqEqualFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(EqGreaterFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(EqLessFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(GreaterFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(LessFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(MinusFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(MultiplyFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(NotFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(OrFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(PlusFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(PrintFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(RestFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(WriterFunction node, Object data) {
		try {
			writer.append(node.code.toString());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}


	@Override
	public Object visit(Atom node, Object data) {
		if(node.type == Atom.var) {			
			varR(node);
		} else {
			literal(node);
		}		
		return null;
	}		

	@Override
	public Object visit(AtomList node, Object data) {		
		return null;
	}

	@Override
	public Object visit(CallBlock node, Object data) {		
		int n = 0;
		Label labelReturn = new Label();		
		ra++;		
		labelReturn.label = "return"+ra;
		try {			
			PrintableStackFrame.pushFrame(writer, ra);
			
			if(this.symbolTable.getScope(node.gotoStmt.label)!=null) {				
				actual = this.symbolTable.getScope(node.gotoStmt.label);
			}
					
			if(node.params.size() > 0)
				PrintableStackFrame.initLocals(writer,node.params.size());			
			writer.append("_rv = (var*)malloc(sizeof(var));\n");
			for(Expr expr: node.params) {
				expr.accept(this, data);
				writer.append("if( _rv->type == v_integer) \n ");								
				writer.append("\tsp->locals["+n+"].value.integer = _rv->value.integer; \n");				
				writer.append("if( _rv->type == v_float) \n ");							
				writer.append("\tsp->locals["+n+"].value.real = _rv->value.real; \n");				
				writer.append("if( _rv->type == v_string) \n ");				
				writer.append("\tsp->locals["+n+"].value.string = _rv->value.string; \n");				
				writer.append("if( _rv->type == v_boolean) \n ");						
				writer.append("\tsp->locals["+n+"].value.boolean = _rv->value.boolean; \n");				
				writer.append("sp->locals["+n+"].type = _rv->type;\n");
				n++;
			}						
			writer.append("_ra=sp->return_address;\n");
			node.gotoStmt.accept(this, data);
			labelReturn.accept(this, data);			
			PrintableStackFrame.popFrame(writer);				
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}	

	@Override
	public Object visit(FORBlock node, Object data) {		
		return null;
	}

	@Override
	public Object visit(FunctionBlock node, Object data) {				
		Goto gotoRedirector = new Goto();
		gotoRedirector.label = "redirector";
		Label skip = new Label();
		skip.label = node.label.label+"_skip";	
		actual = this.symbolTable.getScope(node.label.label);			
		node.skip.accept(this, data);
		node.label.accept(this, data);			
		node.block.accept(this, data);		
		gotoRedirector.accept(this, data);
		skip.accept(this, data);		
		return null;
	}

	@Override
	public Object visit(Goto node, Object data) {
		try {
			writer.append("goto "+node.label+";\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(IFBlock node, Object data) {		
		Goto gotoReturn = new Goto();
		ra++;
		gotoReturn.label = "return"+ra;
		Label labelReturn = new Label();
		labelReturn.label = "return"+ra;		
		try {			
			for(IFStmt n: node.stmts) {
				//Escreve as expressoes
				n.expr.accept(this, data);
				//verifica se as expressoes devolveram verdadeiro
				writer.append("if(_rv->value.boolean == 1) \n");
				n.isTrueGoto.accept(this, data);				
			}			
			//Escreve o bloco else
			if(node.elseBlock != null) {
				//PrintableStackFrame.pushFrame(writer, ra);
				node.elseBlock.accept(this, data);				
			}						
			
			//Escreve os blocos dos ifs
			for(IFStmt n: node.stmts) {
				writer.append("goto skip_"+n.isTrueLabel.label+";\n");
				n.isTrueLabel.accept(this, data);
				//PrintableStackFrame.pushFrame(writer, ra);
				n.block.accept(this, data);						
				gotoReturn.accept(this, data);
				writer.append("skip_"+n.isTrueLabel.label+":\n");
			}											
			labelReturn.accept(this, data);
			//PrintableStackFrame.popFrame(writer);
		} catch (IOException e) {
			e.printStackTrace();
		}			
		return null;
	}

	/**
	 * Está tudo a ser feito no bloco principal, IFBlock
	 */
	@Override
	public Object visit(IFStmt node, Object data) {			
		return null;
	}

	@Override
	public Object visit(Label node, Object data) {
		try {
			writer.append(node.label+" :\n");
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public Object visit(SimpleBlock node, Object data) {
		try {
			for(IRNode n: node.getContent()) {
				writer.append("//**"+n.toString()+"**//\n");
				n.accept(this, data);
			}			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		return null;
	}

	@Override
	public Object visit(WHILEBlock node, Object data) {
		node.assign.accept(this, data);
		node.stmt.accept(this, data);
		node.block.accept(this, data);
		node.elseBlock.accept(this, data);
		node.stmt.accept(this, data);
		return null;
	}
	
	/**
	 * Método auxiliar que retorna true ou false se a variável for global
	 * @param identifier
	 * @return
	 */
	public int isInGlobalScope(String identifier) {
		Scope temp = actual;
		actual = this.symbolTable.getScope("main");
		Symbol symbol = Symbol.getSymbol(identifier);
		int index = -1;
		if(symbol!=null) {
			if(actual.getSymbolIndex(symbol) != -1)
			{						
				index = actual.getSymbolIndex(symbol);	
				actual = temp;
				return index;
			}
		}
		actual = temp;
		return index;
	}
	
	/**
	 * Método auxiliar que retorna true ou false se a variável for local
	 * @param identifier
	 * @return
	 */
	public int isInLocalScope(String identifier) {		
		Symbol symbol = Symbol.getSymbol(identifier);
		int index = -1;
		if(symbol!=null)
			if((index = actual.getSymbolIndex(symbol)) != -1)
				return index;	
		return index;
	}		
	
	public int getIdentifierScope(String identifier) {
		int index = -1;
		return index;
	}
	
	/**
	 * Método que escreve o acesso a uma variável
	 * @param node
	 */
	private void varR(Atom node) {
		Scope temp = actual;
		boolean global = false;
		boolean parent = false;
		int index = 0;		
		try {
			if((index = this.isInLocalScope(node.atom)) == -1) {
				if((index = this.isInGlobalScope(node.atom)) > -1) {
					writer.append("sp = master;\n");					
					global = true;
				}else {					
					writer.append("\\Pop à stack frame para ficar na frame onde a variável existe..\n");
					writer.append("frame *actual_frame = NULL;\n");
					writer.append("actual_frame = sp;\n");
					while((index = this.isInLocalScope(node.atom)) == -1) {												
						PrintableStackFrame.popFrame(writer);
						this.actual = this.actual.getParent();						
					}																	
					parent = true;
				}				
			}
			actual = temp;			
			writer.append("_rv = (var*)malloc(sizeof(var));\n");
			writer.append("if( sp->locals["+index+"].type == v_integer)\n");
			writer.append("\t_rv->value.integer = sp->locals["+index+"].value.integer; \n");
			writer.append("if( sp->locals["+index+"].type == v_string)\n");
			writer.append("\t_rv->value.string = sp->locals["+index+"].value.string; \n");
			writer.append("if( sp->locals["+index+"].type == v_float)\n");
			writer.append("\t_rv->value.real = sp->locals["+index+"].value.real; \n");
			writer.append("if( sp->locals["+index+"].type == v_boolean)\n");
			writer.append("\t_rv->value.boolean = sp->locals["+index+"].value.boolean; \n");
			writer.append("_rv->type = sp->locals["+index+"].type;\n");
			if(global)
				writer.append("sp = fs->content;\n");			
			if(parent) {				
				writer.append("sp = actual_frame;\n");
			}
		} catch(IOException e) {
			e.printStackTrace();
		}		
	}
	
	/**
	 * Método que escreve a escrita numa variável
	 * @param node
	 */
	private void varL(Atom node) {
		Scope temp = actual;
		boolean global = false;
		boolean parent = false;
		int index = 0;		
		try {
			if((index = this.isInLocalScope(node.atom)) == -1) {
				if((index = this.isInGlobalScope(node.atom)) > -1) {
					writer.append("sp = master;\n");					
					global = true;
				}else {					
					writer.append("\\Pop à stack frame para ficar na frame onde a variável existe..\n");
					writer.append("frame *actual_frame = NULL;\n");
					writer.append("actual_frame = sp;\n");
					while((index = this.isInLocalScope(node.atom)) == -1) {												
						PrintableStackFrame.popFrame(writer);
						this.actual = this.actual.getParent();						
					}												
					
					parent = true;
				}				
			}
			actual = temp;
			writer.append("if( _rv->type == v_integer)\n");
			writer.append("\tsp->locals["+index+"].value.integer = _rv->value.integer; \n");
			writer.append("if( _rv->type == v_string)\n");
			writer.append("\tsp->locals["+index+"].value.string = _rv->value.string; \n");
			writer.append("if( _rv->type == v_float)\n");
			writer.append("\tsp->locals["+index+"].value.real = _rv->value.real;\n");
			writer.append("if( _rv->type == v_boolean)\n");
			writer.append("\tsp->locals["+index+"].value.boolean = _rv->value.boolean; \n");
			writer.append("sp->locals["+index+"].type = _rv->type;\n");
			if(global)
				writer.append("sp = fs->content;\n");			
			if(parent) {				
				writer.append("sp = actual_frame;\n");
			}
		} catch(IOException e) {
			e.printStackTrace();
		}		
	}
	
	/**
	 * Método que escreve o acesso a um valor integer, string ou float
	 * @param node
	 */
	private void literal(Atom node) {
		try {
			switch(node.type) {
			case 1: break;
			case 2:
				writer.append("_rv->type = v_integer;\n");				
				writer.append("_rv->value.integer="+node.atom+";\n");
				break;
			case 3:
				writer.append("_rv->type = v_string;\n");
				writer.append("_rv->value.string="+node.atom+";\n");
				break;
			case 4:
				writer.append("_rv->type = v_float;\n");
				writer.append("_rv->value.real="+node.atom+";\n");
				break;
			default:;
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public Object visit(Assignment node, Object data) {										
		node.expr.accept(this, data);		
		varL(node.atom);
		return null;
	}
	
	@Override
	public Object visit(Comparison node, Object data) {	
		return null;
	}

	@Override
	public Object visit(Operation node, Object data) {
		return null;
	}

	@Override
	public Object visit(PrintBlock node, Object data) {
		return null;
	
	}
	
	@Override
	public Object visit(Relational node, Object data) {		
		return null;
	}
	
	@Override
	public Object visit(Not node, Object data) {		
		return null;
	}
}

